package com.codeanalysissys.backend.golang;

import ghaffarian.graphs.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.*;

import com.codeanalysissys.backend.golang.parser.GoLexer;
import com.codeanalysissys.backend.golang.parser.GoParser;
import com.codeanalysissys.backend.graphs.cfg.CFPathTraversal;
import com.codeanalysissys.backend.graphs.pdg.*;


import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;
import com.codeanalysissys.backend.graphs.cfg.CFNode;
import com.codeanalysissys.backend.graphs.cfg.ControlFlowGraph;
import com.codeanalysissys.backend.golang.parser.GoParserBaseVisitor;
import ghaffarian.nanologger.Logger;

/**
 * Data Dependence Graph (DDG) builder for Golang programs.
 * The DDG is actually a subgraph of the Program Dependence Graph (PDG).
 * This implementation is based on ANTLRv4's Visitor pattern.
 *
 * @author Huang WeiQuan
 */
public class GoDDGBuilder {
    private static String currentFile;
    private static Map<String, GoClass> allClassInfos;
    //Golang does not have the class
    private static Map<String, List<MethodDefInfo>> methodDEFs;


    public static DataDependenceGraph build(InputStream inFile) throws IOException {
        Logger.info("Parsing all source files ... ");
        ANTLRInputStream input = new ANTLRInputStream(inFile);
        GoLexer lexer = new GoLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        GoParser parser = new GoParser(tokens);
        ParseTree parseTree = parser.sourceFile();

        Logger.info("Done.");

        // Extract the information of all given Java classes
        Logger.info("\nExtracting class-infos ... ");
        allClassInfos = new HashMap<>();
        List<GoClass[]> filesClasses = new ArrayList<>();
        List<GoClass> classesList = GoClassExtractor.extractInfo(parseTree);
        filesClasses.add(classesList.toArray(new GoClass[classesList.size()]));
        for (GoClass cls : classesList)
            allClassInfos.put(cls.NAME, cls);
        Logger.info("Done.");

        Logger.info("\nInitializing method-DEF infos ... ");
        methodDEFs = new HashMap<>();
        for (GoClass[] classArray : filesClasses) {
            for (GoClass cls : classArray) {
                for (GoMethod mtd : cls.getAllMethods()) {
                    List<MethodDefInfo> list = methodDEFs.get(mtd.NAME);
                    if (list == null) {
                        list = new ArrayList<>();
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.PACKAGE, cls.NAME, mtd.ARG_TYPES));
                        methodDEFs.put(mtd.NAME, list);
                    } else {
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.PACKAGE, cls.NAME, mtd.ARG_TYPES));
                        // no need to do 'methodDEFs.put(...)' again
                    }
                }
            }
        }
        Logger.info("Done.");

        DataDependenceGraph ddg = new DataDependenceGraph();
        //
        Map<ParserRuleContext, Object> pdNode = new IdentityHashMap<>();

        Logger.info("\nIterative DEF-USE analysis ... ");
        boolean changed;
        int iteration = 0;
        do {
            ++iteration;
            changed = false;
            DefUseVisitor defUse = new DefUseVisitor(iteration, filesClasses.get(0), ddg, pdNode);
            defUse.visit(parseTree);
            changed |= defUse.changed;
            Logger.debug("Iteration #" + iteration + ": " + (changed ? "CHANGED" : "NO-CHANGE"));
            Logger.debug("\n========================================\n");
        } while (changed);
        Logger.info("Done.");

        // Build control-flow graphs for all Java files including the extracted DEF-USE info ...
        Logger.info("\nExtracting CFGs ... ");
        ControlFlowGraph cfg = GoCFGBuilder.build(parseTree, "pdnode", pdNode);
        Logger.info("Done.");

        // Finally, traverse all control-flow paths and draw data-flow dependency edges ...
        Logger.info("\nAdding data-flow edges ... ");
        addDataFlowEdges(cfg, ddg);
        ddg.attachCFG(cfg);
        Logger.info("Done.\n");

        return ddg;


    }

    public static DataDependenceGraph buildForOne(String fileName, InputStream inputStream) throws IOException {
        DataDependenceGraph[] ddgs = buildForAll(fileName, inputStream);
        return ddgs[0];
    }

    public static DataDependenceGraph[] buildForAll(String fileName, InputStream inputStream) throws IOException {
        int[] files = new int[1];
        // Parse all Java source files
        Logger.info("Parsing all source files ... ");
        ParseTree[] parseTrees = new ParseTree[files.length];

        for (int i = 0; i < files.length; ++i) {
            InputStream inFile = inputStream;
            ANTLRInputStream input = new ANTLRInputStream(inFile);
            GoLexer lexer = new GoLexer(input);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            GoParser parser = new GoParser(tokens);
            parseTrees[i] = parser.sourceFile();
        }
        Logger.info("Done.");

        // Extract the information of all given Java classes
        Logger.info("\nExtracting class-infos ... ");
        allClassInfos = new HashMap<>();
        List<GoClass[]> filesClasses = new ArrayList<>();
        for (int i = 0; i < files.length; ++i)
        {
            List<GoClass> classesList = GoClassExtractor.extractInfo(fileName, parseTrees[i]);
            filesClasses.add(classesList.toArray(new GoClass[classesList.size()]));
            for (GoClass cls: classesList)
                allClassInfos.put(cls.NAME, cls);
        }
        Logger.info("Done.");

        Logger.info("\nInitializing method-DEF infos ... ");
        methodDEFs = new HashMap<>();
        for (GoClass[] classArray: filesClasses) {
            for (GoClass cls : classArray) {
                for (GoMethod mtd : cls.getAllMethods()) {
                    List<MethodDefInfo> list = methodDEFs.get(mtd.NAME);
                    if (list == null)
                    {
                        list = new ArrayList<>();
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.PACKAGE, cls.NAME, mtd.ARG_TYPES));
                        methodDEFs.put(mtd.NAME, list);
                    } else
                    {
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.PACKAGE, cls.NAME, mtd.ARG_TYPES));
                        // no need to do 'methodDEFs.put(...)' again
                    }
                }
            }
        }
        Logger.info("Done.");

        DataDependenceGraph[] ddgs = new DataDependenceGraph[files.length];
        for (int i = 0; i < ddgs.length; ++i)
            ddgs[i] = new DataDependenceGraph(fileName);
        //
        Map<ParserRuleContext, Object>[] pdNodes = new Map[parseTrees.length];
        for (int i = 0; i < parseTrees.length; ++i)
            pdNodes[i] = new IdentityHashMap<>();

        Logger.info("\nIterative DEF-USE analysis ... ");
        boolean changed;
        int iteration = 0;
        do {
            ++iteration;
            changed = false;
            for (int i = 0; i < files.length; ++i) {
                currentFile = fileName;
                DefUseVisitor defUse = new DefUseVisitor(iteration, filesClasses.get(i), ddgs[i], pdNodes[i]);
                defUse.visit(parseTrees[i]);
                changed |= defUse.changed;
            }
            Logger.debug("Iteration #" + iteration + ": " + (changed ? "CHANGED" : "NO-CHANGE"));
            Logger.debug("\n========================================\n");
        } while (changed);
        Logger.info("Done.");

        // Build control-flow graphs for all Java files including the extracted DEF-USE info ...
        Logger.info("\nExtracting CFGs ... ");
        ControlFlowGraph[] cfgs = new ControlFlowGraph[files.length];
        for (int i = 0; i < files.length; ++i)
            cfgs[i] = GoCFGBuilder.build(fileName, parseTrees[i], "pdnode", pdNodes[i]);
        Logger.info("Done.");

        // Finally, traverse all control-flow paths and draw data-flow dependency edges ...
        Logger.info("\nAdding data-flow edges ... ");
        for (int i = 0; i < files.length; ++i) {
            addDataFlowEdges(cfgs[i], ddgs[i]);
            ddgs[i].attachCFG(cfgs[i]);
        }
        Logger.info("Done.\n");

        return ddgs;


    }

    public static DataDependenceGraph[] buildForAll(File[] files) throws IOException {
        Logger.info("Parsing all source files ... ");
        ParseTree[] parseTrees = new ParseTree[files.length];
        for (int i = 0; i < files.length; ++i) {
            CharStream input = CharStreams.fromPath(Paths.get(files[i].getAbsolutePath()));
            GoLexer lexer = new GoLexer(input);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            GoParser parser = new GoParser(tokens);
            parseTrees[i] = parser.sourceFile();
        }
        Logger.info("Done.");

        // Extract the information of all given Java classes
        Logger.info("\nExtracting class-infos ... ");
        allClassInfos = new HashMap<>();
        List<GoClass[]> filesClasses = new ArrayList<>();
        for (int i = 0; i < files.length; ++i)
        {
            List<GoClass> classesList = GoClassExtractor.extractInfo(files[i].getPath(), parseTrees[i]);
            filesClasses.add(classesList.toArray(new GoClass[classesList.size()]));
            for (GoClass cls: classesList)
                allClassInfos.put(cls.NAME, cls);
        }
        Logger.info("Done.");

        Logger.info("\nInitializing method-DEF infos ... ");
        methodDEFs = new HashMap<>();
        for (GoClass[] classArray: filesClasses) {
            for (GoClass cls : classArray) {
                for (GoMethod mtd : cls.getAllMethods()) {
                    List<MethodDefInfo> list = methodDEFs.get(mtd.NAME);
                    if (list == null)
                    {
                        list = new ArrayList<>();
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.PACKAGE, cls.NAME, mtd.ARG_TYPES));
                        methodDEFs.put(mtd.NAME, list);
                    } else
                    {
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.PACKAGE, cls.NAME, mtd.ARG_TYPES));
                        // no need to do 'methodDEFs.put(...)' again
                    }
                }
            }
        }
        Logger.info("Done.");

        DataDependenceGraph[] ddgs = new DataDependenceGraph[files.length];
        for (int i = 0; i < ddgs.length; ++i)
            ddgs[i] = new DataDependenceGraph(files[i].getName());
        //
        Map<ParserRuleContext, Object>[] pdNodes = new Map[parseTrees.length];
        for (int i = 0; i < parseTrees.length; ++i)
            pdNodes[i] = new IdentityHashMap<>();

        Logger.info("\nIterative DEF-USE analysis ... ");
        boolean changed;
        int iteration = 0;
        do {
            ++iteration;
            changed = false;
            for (int i = 0; i < files.length; ++i) {
                currentFile = files[i].getName();
                DefUseVisitor defUse = new DefUseVisitor(iteration, filesClasses.get(i), ddgs[i], pdNodes[i]);
                defUse.visit(parseTrees[i]);
                changed |= defUse.changed;
            }
            Logger.debug("Iteration #" + iteration + ": " + (changed ? "CHANGED" : "NO-CHANGE"));
            Logger.debug("\n========================================\n");
        } while (changed);
        Logger.info("Done.");

        // Build control-flow graphs for all Java files including the extracted DEF-USE info ...
        Logger.info("\nExtracting CFGs ... ");
        ControlFlowGraph[] cfgs = new ControlFlowGraph[files.length];
        for (int i = 0; i < files.length; ++i)
            cfgs[i] = GoCFGBuilder.build(files[i].getName(), parseTrees[i], "pdnode", pdNodes[i]);
        Logger.info("Done.");

        // Finally, traverse all control-flow paths and draw data-flow dependency edges ...
        Logger.info("\nAdding data-flow edges ... ");
        for (int i = 0; i < files.length; ++i) {
            addDataFlowEdges(cfgs[i], ddgs[i]);
            ddgs[i].attachCFG(cfgs[i]);
        }
        Logger.info("Done.\n");

        return ddgs;


    }

    private static int countSlashes(String str) {
        int slashCount = 0;
        for (char chr: str.toCharArray())
            if (chr == '/')
                ++slashCount;
        return slashCount;
    }

    private static void addDataFlowEdges(ControlFlowGraph cfg, DataDependenceGraph ddg) {
        Set<CFNode> visitedDefs = new LinkedHashSet<>();
        for (CFNode entry: cfg.getAllMethodEntries()) {
            visitedDefs.clear();
            CFPathTraversal defTraversal = new CFPathTraversal(cfg, entry);
            while (defTraversal.hasNext()) {
                CFNode defCFNode = defTraversal.next();
                if (!visitedDefs.add(defCFNode)) {
                    defTraversal.continueNextPath();
                    continue;
                }
                PDNode defNode = (PDNode) defCFNode.getProperty("pdnode");
                if (defNode == null) {
                    //Logger.debug("No PDNode: " + defCFNode);
                    continue;
                }
                if (defNode.getAllDEFs().length == 0)
                    continue;
                // first add any self-flows of this node
                for (String flow: defNode.getAllSelfFlows()) {
                    ddg.addEdge(new Edge<>(defNode, new DDEdge(DDEdge.Type.FLOW, flow), defNode));
                }
                // now traverse the CFG for any USEs till a DEF
                Set<CFNode> visitedUses = new LinkedHashSet<>();
                for (String def: defNode.getAllDEFs()) {
                    CFPathTraversal useTraversal = new CFPathTraversal(cfg, defCFNode);
                    visitedUses.clear();
                    CFNode useCFNode = useTraversal.next(); // skip start node
                    visitedUses.add(useCFNode);
                    while (useTraversal.hasNext()) {
                        useCFNode = useTraversal.next();
                        PDNode useNode = (PDNode) useCFNode.getProperty("pdnode");
                        if (useNode == null) {
                            //Logger.debug("No PDNode: " + useCFNode);
                            continue;
                        }
                        if (useNode.hasDEF(def))
                            useTraversal.continueNextPath(); // no need to continue this path
                        if (!visitedUses.add(useCFNode))
                            useTraversal.continueNextPath(); // no need to continue this path
                        else
                        if (useNode.hasUSE(def))
                            ddg.addEdge(new Edge<>(defNode, new DDEdge(DDEdge.Type.FLOW, def), useNode));
                    }
                }
            }
        }
    }


    private static class DefUseVisitor extends GoParserBaseVisitor<String>
    {
        private static final int PARAM = 1;
        private static final int FIELD = 101;
        private static final int LOCAL = 202;
        private static final int OUTER = 303;

        private int iteration;
        private boolean changed;
        private boolean analysisVisit;
        private GoClass[] classInfos;
        private DataDependenceGraph ddg;
        private Set<String> defList, useList, selfFlowList;
        private Map<ParserRuleContext, Object> pdNodes;
        private Deque<GoClass> activeClasses;
        private MethodDefInfo methodDefInfo;
        private GoField[] methodParams;
        private List<GoField> localVars;

        public DefUseVisitor(int iter, GoClass[] classInfos,
                             DataDependenceGraph ddg, Map<ParserRuleContext, Object> pdNodes) {
            Logger.debug("FILE IS: " + currentFile);
            this.ddg = ddg;
            changed = false;
            iteration = iter;
            analysisVisit = false;
            this.pdNodes = pdNodes;
            this.classInfos = classInfos;
            defList = new LinkedHashSet<>();
            useList = new LinkedHashSet<>();
            selfFlowList = new LinkedHashSet<>();
            activeClasses = new ArrayDeque<>();
            methodDefInfo = null;
            methodParams = new GoField[0];
            localVars = new ArrayList<>();
            GoClass cls=classInfos[0];
            activeClasses.push(cls);
        }

        private void analyseDefUse(PDNode node, ParseTree expression)
    {
        Logger.debug("--- ANALYSIS ---");
        Logger.debug(node.toString());
        analysisVisit = true;
        String expr="$";
        if(expression!=null)
        {
            expr = visit(expression);
            Logger.debug(expr);
        }
        //
        StringBuilder locVarsStr = new StringBuilder(256);
        locVarsStr.append("LOCAL VARS = [");
        for (GoField lv: localVars)
            locVarsStr.append(lv.TYPE).append(' ').append(lv.NAME).append(", ");
        locVarsStr.append("]");
        Logger.debug(locVarsStr.toString());
        //
        if (isUsableExpression(expr))
        {
            useList.add(expr);
            Logger.debug("USABLE");
        }
        analysisVisit = false;
        Logger.debug("Changed = " + changed);
        Logger.debug("DEFs = " + Arrays.toString(node.getAllDEFs()));
        Logger.debug("USEs = " + Arrays.toString(node.getAllUSEs()));
        for (String def: defList) {
            int status = isDefined(def);
            if (status > -1) {
                if (status < 100) {
                    methodDefInfo.setArgDEF(status, true);
                    Logger.debug("Method defines argument #" + status);
                } else if (status == FIELD) {
                    methodDefInfo.setStateDEF(true);
                    if (def.startsWith("this."))
                        def = def.substring(5);
                    def = "$THIS." + def;
                    Logger.debug("Method defines object state.");
                }
                changed |= node.addDEF(def);
            }
            else
                Logger.debug(def + " is not defined!");
        }
        Logger.debug("Changed = " + changed);
        Logger.debug("DEFs = " + Arrays.toString(node.getAllDEFs()));
        //
        for (String use: useList) {
            int status = isDefined(use);
            if (status > -1) {
                if (status == FIELD) {
                    if (use.startsWith("this."))
                        use = use.substring(5);
                    use = "$THIS." + use;
                }
                changed |= node.addUSE(use);
            } else
                Logger.debug(use + " is not defined!");
        }
        Logger.debug("Changed = " + changed);
        Logger.debug("USEs = " + Arrays.toString(node.getAllUSEs()));
        //
        for (String flow: selfFlowList) {
            int status = isDefined(flow);
            if (status > -1) {
                if (status == FIELD) {
                    if (flow.startsWith("this."))
                        flow = flow.substring(5);
                    flow = "$THIS." + flow;
                }
                changed |= node.addSelfFlow(flow);
            } else
                Logger.debug(flow + " is not defined!");
        }
        Logger.debug("Changed = " + changed);
        Logger.debug("SELF_FLOWS = " + Arrays.toString(node.getAllSelfFlows()));
        defList.clear();
        useList.clear();
        selfFlowList.clear();
        Logger.debug("----------------");
    }

        private String getType(String id) {
            if (isUsableExpression(id)) {
                for (GoField param: methodParams)
                    if (param.NAME.equals(id))
                        return param.TYPE;
                for (GoField local: localVars)
                    if (local.NAME.equals(id))
                        return local.TYPE;
                if (id.startsWith("this."))
                    id = id.substring(4);
                for (GoField field: activeClasses.peek().getAllFields())
                    if (field.NAME.equals(id))
                        return field.TYPE;
                for (GoClass cls: activeClasses)
                    for (GoField field: cls.getAllFields())
                        if (field.NAME.equals(id))
                            return field.TYPE;
                Logger.debug("getType(" + id + ") : is USABLE but NOT DEFINED");
                return null;
            } else {
                Logger.debug("getType(" + id + ") : is NOT USABLE");
                return null;
            }
        }

        private void analyseDefUse1(PDNode node, GoParser.ShortVarDeclContext expression)
        {
            Logger.debug("--- ANALYSIS ---");
            Logger.debug(node.toString());
            analysisVisit = true;
            String expr="$";
            if(expression!=null)
            {
                expr = visitSvarDecl(expression);
                Logger.debug(expr);
            }
            //
            StringBuilder locVarsStr = new StringBuilder(256);
            locVarsStr.append("LOCAL VARS = [");
            for (GoField lv: localVars)
                locVarsStr.append(lv.TYPE).append(' ').append(lv.NAME).append(", ");
            locVarsStr.append("]");
            Logger.debug(locVarsStr.toString());
            //
            if (isUsableExpression(expr))
            {
                useList.add(expr);
                Logger.debug("USABLE");
            }
            analysisVisit = false;
            Logger.debug("Changed = " + changed);
            Logger.debug("DEFs = " + Arrays.toString(node.getAllDEFs()));
            Logger.debug("USEs = " + Arrays.toString(node.getAllUSEs()));
            for (String def: defList) {
                int status = isDefined(def);
                if (status > -1) {
                    if (status < 100) {
                        methodDefInfo.setArgDEF(status, true);
                        Logger.debug("Method defines argument #" + status);
                    } else if (status == FIELD) {
                        methodDefInfo.setStateDEF(true);
                        if (def.startsWith("this."))
                            def = def.substring(5);
                        def = "$THIS." + def;
                        Logger.debug("Method defines object state.");
                    }
                    changed |= node.addDEF(def);
                }
                else
                    Logger.debug(def + " is not defined!");
            }
            Logger.debug("Changed = " + changed);
            Logger.debug("DEFs = " + Arrays.toString(node.getAllDEFs()));
            //
            for (String use: useList) {
                int status = isDefined(use);
                if (status > -1) {
                    if (status == FIELD) {
                        if (use.startsWith("this."))
                            use = use.substring(5);
                        use = "$THIS." + use;
                    }
                    changed |= node.addUSE(use);
                } else
                    Logger.debug(use + " is not defined!");
            }
            Logger.debug("Changed = " + changed);
            Logger.debug("USEs = " + Arrays.toString(node.getAllUSEs()));
            //
            for (String flow: selfFlowList) {
                int status = isDefined(flow);
                if (status > -1) {
                    if (status == FIELD) {
                        if (flow.startsWith("this."))
                            flow = flow.substring(5);
                        flow = "$THIS." + flow;
                    }
                    changed |= node.addSelfFlow(flow);
                } else
                    Logger.debug(flow + " is not defined!");
            }
            Logger.debug("Changed = " + changed);
            Logger.debug("SELF_FLOWS = " + Arrays.toString(node.getAllSelfFlows()));
            defList.clear();
            useList.clear();
            selfFlowList.clear();
            Logger.debug("----------------");
        }

        private boolean isUsableExpression(String expr)
        {
            // must not be a literal or of type 'class'.
            if (expr.startsWith("$"))
                return false;
            // must not be a method-call or parenthesized expression
            if (expr.endsWith(")"))
                return false;
            // must not be an array-indexing expression
            if (expr.endsWith("]"))
                return false;
            // must not be post unary operation expression
            if (expr.endsWith("++") || expr.endsWith("--"))
                return false;
            // must not be a pre unary operation expression
            if (expr.startsWith("+") || expr.startsWith("-") || expr.startsWith("!") || expr.startsWith("~"))
                return false;
            // must not be an array initialization expression
            if (expr.endsWith("}"))
                return false;
            // must not be an explicit generic invocation expression
            if (expr.startsWith("<"))
                return false;
            //
            return true;
        }

        private int isDefined(String id)
        {
            for (int i = 0; i < methodParams.length; ++i)
                if (methodParams[i].NAME.equals(id))
                    return i;
            for (GoField local: localVars)
                if (local.NAME.equals(id))
                    return LOCAL;
            if (id.startsWith("this."))
                id = id.substring(5);
            for (GoField field: activeClasses.peek().getAllFields())
                if (field.NAME.equals(id))
                    return FIELD;
            for (GoClass cls: activeClasses)
                for (GoField field: cls.getAllFields())
                    if (field.NAME.equals(id))
                        return OUTER;
            return -1;
        }

        private MethodDefInfo findDefInfo(String callee, String name, GoParser.ExpressionListContext ctx)
        {
            List<MethodDefInfo> list = methodDEFs.get(name);
            Logger.debug("METHOD NAME: " + name);
            Logger.debug("# found = " + (list == null ? 0 : list.size()));
            //
            if (list == null)
                return null;
            //
            if (list.size() == 1) { // only one candidate
                Logger.debug("SINGLE CANDIDATE");
                MethodDefInfo mtd = list.get(0);
                // just check params-count to make sure
                if (ctx != null && mtd.PARAM_TYPES != null &&
                        mtd.PARAM_TYPES.length != ctx.expression().size())
                    return null;
                Logger.debug("WITH MATCHING PARAMS COUNT");
                return mtd;
            }
            //
            if (callee == null) { // no callee; so search for self methods
                Logger.debug("NO CALLEE");
                forEachDefInfo:
                for (MethodDefInfo mtd : list) {
                    // check package-name
                    if (!mtd.PACKAGE.equals(activeClasses.peek().PACKAGE))
                        continue;
                    // check class-name
                    boolean classNameMatch = false;
                    for (GoClass cls: activeClasses) {
                        if (mtd.CLASS_NAME.equals(cls.NAME)) {
                            classNameMatch = true;
                            break;
                        }
                    }
                    if (!classNameMatch)
                        continue;
                    // check params-count
                    if (ctx != null && mtd.PARAM_TYPES != null &&
                            mtd.PARAM_TYPES.length != ctx.expression().size())
                        continue;
                    // check params-types
                    if (ctx != null) {
                        String[] argTypes = new String[ctx.expression().size()];
                        for (int i = 0; i < argTypes.length; ++i) {
                            String arg = visit(ctx.expression(i));
                            argTypes[i] = getType(arg);
                        }
                        if (mtd.PARAM_TYPES != null) {
                            for (int i = 0; i < argTypes.length; ++i) {
                                if (argTypes[i] == null)
                                    continue;
                                if (!argTypes[i].equals(mtd.PARAM_TYPES[i]))
                                    continue forEachDefInfo;
                            }
                        }
                    }
                    return mtd;
                }
            } else if (isDefined(callee) > -1) { // has a defined callee
                Logger.debug("DEFINED CALLEE");
                String type = getType(callee);
                GoClass cls = allClassInfos.get(type);
                if (cls != null && cls.hasMethod(name)) {
                    forEachDefInfo:
                    for (MethodDefInfo mtd : list) {
                        // check package-name
                        if (!mtd.PACKAGE.equals(cls.PACKAGE))
                            continue;
                        // check class-name
                        if (!mtd.CLASS_NAME.equals(cls.NAME))
                            continue;
                        // check params-count
                        if (ctx != null && mtd.PARAM_TYPES != null &&
                                mtd.PARAM_TYPES.length != ctx.expression().size())
                            continue;
                        // check params-types
                        if (ctx != null) {
                            String[] argTypes = new String[ctx.expression().size()];
                            for (int i = 0; i < argTypes.length; ++i) {
                                String arg = visit(ctx.expression(i));
                                argTypes[i] = getType(arg);
                            }
                            if (mtd.PARAM_TYPES != null) {
                                for (int i = 0; i < argTypes.length; ++i) {
                                    if (argTypes[i] == null)
                                        continue;
                                    if (!argTypes[i].equals(mtd.PARAM_TYPES[i]))
                                        continue forEachDefInfo;
                                }
                            }
                        }
                        return mtd;
                    }
                    Logger.debug("METHOD DEF INFO NOT FOUND!");
                } else {
                    Logger.debug((cls == null ?
                            "CLASS OF TYPE " + type + " NOT FOUND!" :
                            "CLASS HAS NO SUCH METHOD!"));
                }
            } else { // has an undefined callee
                Logger.debug("UNDEFINED CALLEE.");
                //
                // TODO: use a global retType for visiting expressions
                //
            }
            return null;
        }

        private MethodDefInfo findDefInfo(String name, String type, GoField[] params) {
            List<MethodDefInfo> infoList = methodDEFs.get(name);
            if(infoList!=null)
            {
                if (infoList.size() > 1) {
                    forEachInfo:
                    for (MethodDefInfo info : infoList) {
                    if (!info.PACKAGE.equals(activeClasses.peek().PACKAGE))
                        continue;
                    if (!info.CLASS_NAME.equals(activeClasses.peek().NAME))
                        continue;
                        if ((info.RET_TYPE == null && type != null) ||
                                (info.RET_TYPE != null && type == null))
                            continue;
                        if (type != null && !type.startsWith(info.RET_TYPE))
                            continue;
                        if (info.PARAM_TYPES != null) {
                            if (info.PARAM_TYPES.length != params.length)
                                continue;
                            for (int i = 0; i < params.length; ++i)
                                if (!params[i].TYPE.startsWith(info.PARAM_TYPES[i]))
                                    continue forEachInfo;
                        } else if (params.length > 0)
                            continue;
                        return info;
                    }
                } else if (infoList.size() == 1)
                    return infoList.get(0);
            }
            return null;
        }

        /*****************************************************
         *****************************************************
         *****************************************************/

        /**
         * Get the original program text for the given parser-rule context.
         * This is required for preserving whitespaces.
         **/
        private String getOriginalCodeText(ParserRuleContext ctx) {
            int start = ctx.start.getStartIndex();
            int stop = ctx.stop.getStopIndex();
            Interval interval = new Interval(start, stop);
            return ctx.start.getInputStream().getText(interval);
        }


        @Override
        public String visitFunctionDecl(GoParser.FunctionDeclContext ctx)
        {
            PDNode entry;
            if (iteration == 1) {
                entry = new PDNode();
                entry.setLineOfCode(ctx.getStart().getLine());
                String retType = "func";
                String args=" ";
                if(ctx.formalParameters()!=null)
                {
                    args = getOriginalCodeText(ctx.formalParameters());
                }
                entry.setCode(retType + " " + ctx.IDENTIFIER().getText() + args);
                entry.setProperty("name", ctx.IDENTIFIER().getText());
                entry.setProperty("type", retType);
                ddg.addVertex(entry);
                pdNodes.put(ctx, entry);
                //
                // Extract all parameter types and IDs
                List<String> paramIDs = new ArrayList<>();
                List<String> paramTypes = new ArrayList<>();
                if (ctx.signature()!= null&& ctx.signature().parameters(0)!=null) {
                    for (GoParser.ParametersContext prm :
                            ctx.signature().parameters(0)) {
                        if(prm.parameterDecl(0)==null)
                        {

                        }
                        else
                        {
                            paramTypes.add(visitType(prm.parameterDecl(0).type_()));
                            paramIDs.add(prm.parameterDecl(0).identifierList().getText());
                        }
                    }
                }
                methodParams = new GoField[paramIDs.size()];
                for (int i = 0; i < methodParams.length; ++i)
                    methodParams[i] = new GoField(null, false, paramTypes.get(i), paramIDs.get(i));
                entry.setProperty("params", methodParams);
                //
                // Add initial DEF info: method entry nodes define the input-parameters
                for (String pid: paramIDs)
                    changed |= entry.addDEF(pid);
            } else {
                entry = (PDNode) pdNodes.get(ctx);
                methodParams = (GoField[]) entry.getProperty("params");
            }

            methodDefInfo = findDefInfo((String) entry.getProperty("name"),
                    (String) entry.getProperty("type"),	methodParams);
            if (methodDefInfo == null) {
                Logger.error("Function NOT FOUND!");
                Logger.error("NAME = " + (String) entry.getProperty("name"));
                Logger.error("TYPE = " + (String) entry.getProperty("type"));
                Logger.error("PARAMS = " + Arrays.toString(methodParams));
                if (activeClasses.peek() != null) {
                    Logger.error("PACKAGE = " + activeClasses.peek().PACKAGE);
                }
                List list = methodDEFs.get((String) entry.getProperty("name"));
                if(list!=null) {
                    for (int i = 0; i < list.size(); ++i)
                        Logger.error(list.get(i).toString());
                }
            }

            // Now visit method body ...
            localVars.clear();
            if (ctx.block() != null)
                visit(ctx.block());
            //
            localVars.clear();
            methodParams = new GoField[0];
            return null;
        }

        private String visitType(GoParser.Type_Context ctx) {
            // typeType :  classOrInterfaceType ('[' ']')*  |   primitiveType ('[' ']')*
            //
            // classOrInterfaceType :  Identifier typeArguments? ('.' Identifier typeArguments? )*
            //
            return ctx.getText();
        }

        @Override
        public String visitShortVarDecl(GoParser.ShortVarDeclContext ctx)
        {

            int size=0;
            for(;size<ctx.identifierList().IDENTIFIER().size();size++) {
                localVars.add(new GoField(null, false, "undefined",
                        ctx.identifierList().IDENTIFIER(size).getText()));
            }
                if(analysisVisit)
                    return visitSvarDecl(ctx);
                PDNode declr;
                if (iteration == 1) {
                    declr = new PDNode();
                    declr.setLineOfCode(ctx.getStart().getLine());
                    declr.setCode(getOriginalCodeText(ctx));
                    ddg.addVertex(declr);
                    pdNodes.put(ctx, declr);
                } else {
                    declr = (PDNode) pdNodes.get(ctx);
                }
                analyseDefUse1(declr, ctx);
                return null;
        }

        private String visitSvarDecl(GoParser.ShortVarDeclContext ctx)
        {
            String init = "";
            int size=0;
            String name="";
            while(size<ctx.identifierList().IDENTIFIER().size())
            {
                String varID=ctx.identifierList().IDENTIFIER(size).getText();
                if (ctx.expressionList() != null)
                    init = visit(ctx.expressionList());
                if (isUsableExpression(init))
                    useList.add(init);
                defList.add(varID);
                init = " $INIT " + init;
                if(size==0)
                    name=name+varID;
                else
                    name=name+","+varID;
                size++;
            }
            return "$VAR " + name + init;
        }

        @Override
        public String visitVarDecl(GoParser.VarDeclContext ctx) {
            int size=0;
            for(;size<ctx.varSpec(0).identifierList().IDENTIFIER().size();size++) {
                localVars.add(new GoField(null, false, visitType(ctx.varSpec(0).type_()),
                        ctx.varSpec(0).identifierList().IDENTIFIER(size).getText()));
            }
            if (analysisVisit)
                return visit(ctx.varSpec(0));
            PDNode declr;
            if (iteration == 1) {
                declr = new PDNode();
                declr.setLineOfCode(ctx.getStart().getLine());
                declr.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(declr);
                pdNodes.put(ctx, declr);
            } else
                declr = (PDNode) pdNodes.get(ctx);
            analyseDefUse(declr, ctx.varSpec(0));
            return null;
        }

        @Override
        public String visitVarSpec(GoParser.VarSpecContext ctx)
        {
            String init = "";
            int size=0;
            String name="";
            while(size<ctx.identifierList().IDENTIFIER().size())
            {
                String varID=ctx.identifierList().IDENTIFIER(size).getText();
                if (ctx.expressionList() != null)
                    init = visit(ctx.expressionList());
                if (isUsableExpression(init))
                    useList.add(init);
                defList.add(varID);
                init = " $INIT " + init;
                if(size==0)
                name=name+varID;
                else
                    name=name+","+varID;
                size++;
            }
            return "$VAR " + name + init;
        }

        @Override
        public String visitBlock(GoParser.BlockContext ctx)
        {
            int entrySize = localVars.size();
            //
            visitChildren(ctx);
            //
            if (localVars.size() > entrySize)
                localVars.subList(entrySize, localVars.size()).clear();
            return null;
        }

        @Override
        public String visitExpressionStmt(GoParser.ExpressionStmtContext ctx)
        {
            if (analysisVisit)
                return visit(ctx.expression());
            PDNode expr;
            if (iteration == 1) {
                expr = new PDNode();
                expr.setLineOfCode(ctx.getStart().getLine());
                expr.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(expr);
                pdNodes.put(ctx, expr);
            } else
                expr = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            analyseDefUse(expr, ctx.expression());
            return null;
        }

        @Override
        public String visitSimpleStmt(GoParser.SimpleStmtContext ctx)
        {

            if (analysisVisit)
            {
                if (ctx.assignment() != null)
                    return visit(ctx.assignment());
                else if (ctx.incDecStmt() != null)
                    return visit(ctx.incDecStmt());
                else if (ctx.sendStmt() != null)
                    return visit(ctx.sendStmt());
                else if (ctx.shortVarDecl() != null)
                    return visit(ctx.shortVarDecl());
                else
                    return visit(ctx.expressionStmt());
            }
            /*if (ctx.assignment() != null)
                visit(ctx.assignment());
            else if (ctx.incDecStmt() != null)
                visit(ctx.incDecStmt());
            else if (ctx.sendStmt() != null)
                visit(ctx.sendStmt());
            if (ctx.shortVarDecl() != null)
                visit(ctx.shortVarDecl());
            else if (ctx.expressionStmt()!=null)
                visit(ctx.expressionStmt());*/
            if (ctx.shortVarDecl() != null)
                return visit(ctx.shortVarDecl());
            PDNode expr;
            if (iteration == 1)
            {
                expr = new PDNode();
                expr.setLineOfCode(ctx.getStart().getLine());
                expr.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(expr);
                pdNodes.put(ctx, expr);
            } else
                expr = (PDNode) pdNodes.get(ctx);
            if(ctx.assignment()!=null)
                analyseDefUse(expr, ctx.assignment());
            else if(ctx.expressionStmt()!=null)
            analyseDefUse(expr, ctx.expressionStmt());
            else if(ctx.incDecStmt()!=null)
                analyseDefUse(expr, ctx.incDecStmt());
            else if(ctx.sendStmt()!=null)
                analyseDefUse(expr, ctx.sendStmt());
            //
            // Now analyse DEF-USE by visiting the expression ...
            return null;
        }

        @Override
        public String visitIfStmt(GoParser.IfStmtContext ctx)
        {
            PDNode ifNode;
            if (iteration == 1)
            {
                ifNode = new PDNode();
                ifNode.setLineOfCode(ctx.getStart().getLine());
                ifNode.setCode("if " + getOriginalCodeText(ctx.expression()));
                ddg.addVertex(ifNode);
                pdNodes.put(ctx, ifNode);
            } else
                ifNode = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            analyseDefUse(ifNode, ctx.expression());
            //
            for (GoParser.BlockContext stmnt: ctx.block())
                visit(stmnt);
            return null;
        }

        @Override
        public String visitForStmt(GoParser.ForStmtContext ctx)
        {
            int entrySize = localVars.size();
            if (ctx.forClause()!= null)
            {
                if (ctx.forClause().initStmt!= null) { // non-empty init
                    PDNode forInit;
                    if (iteration == 1) {
                        forInit = new PDNode();
                        forInit.setLineOfCode(ctx.forClause().initStmt.getStart().getLine());
                        forInit.setCode(getOriginalCodeText(ctx.forClause().initStmt));
                        ddg.addVertex(forInit);
                        pdNodes.put(ctx.forClause().initStmt, forInit);
                    } else
                        forInit = (PDNode) pdNodes.get(ctx.forClause().initStmt);
                    //
                    // Now analyse DEF-USE by visiting the expression ...
                    if (ctx.forClause().initStmt.expressionStmt() != null)
                        analyseDefUse(forInit, ctx.forClause().initStmt.expressionStmt());
                    else
                        analyseDefUse(forInit, ctx.forClause().initStmt.shortVarDecl());
                }
                // for-expression
                if (ctx.forClause().expression() != null) { // non-empty predicate-expression
                    PDNode forExpr;
                    if (iteration == 1) {
                        forExpr = new PDNode();
                        forExpr.setLineOfCode(ctx.forClause().expression().getStart().getLine());
                        forExpr.setCode("for (" + getOriginalCodeText(ctx.forClause().expression()) + ")");
                        ddg.addVertex(forExpr);
                        pdNodes.put(ctx.forClause().expression(), forExpr);
                    } else
                        forExpr = (PDNode) pdNodes.get(ctx.forClause().expression());
                    //
                    // Now analyse DEF-USE by visiting the expression ...
                    analyseDefUse(forExpr, ctx.forClause().expression());
                }
                // for-update
                if (ctx.forClause().postStmt != null) { // non-empty for-update
                    PDNode forUpdate;
                    if (iteration == 1) {
                        forUpdate = new PDNode();
                        forUpdate.setCode(getOriginalCodeText(ctx.forClause().postStmt));
                        forUpdate.setLineOfCode(ctx.forClause().postStmt.getStart().getLine());
                        ddg.addVertex(forUpdate);
                        pdNodes.put(ctx.forClause().postStmt, forUpdate);
                    } else
                        forUpdate = (PDNode) pdNodes.get(ctx.forClause().postStmt);
                    //
                    // Now analyse DEF-USE by visiting the expression ...
                    analyseDefUse(forUpdate, ctx.forClause().postStmt.expressionStmt());
                }
            }
            else
            {
                PDNode forExpr;
                if (iteration == 1) {
                    forExpr = new PDNode();
                    forExpr.setLineOfCode(ctx.expression().getStart().getLine());
                    forExpr.setCode("for (" + getOriginalCodeText(ctx.expression()) + ")");
                    ddg.addVertex(forExpr);
                    pdNodes.put(ctx.expression(), forExpr);
                } else
                    forExpr = (PDNode) pdNodes.get(ctx.expression());
                //
                // Now analyse DEF-USE by visiting the expression ...
                /*String type = "Judgement";
                String var = ctx.expression().getText();
                localVars.add(new GoField(null, false, type, var));
                changed |= forExpr.addDEF(var);*/
                analyseDefUse(forExpr, ctx.expression());
            }
            String visit = visit(ctx.block());
            // clear any local vars defined in the for loop
            if (localVars.size() > entrySize)
                localVars.subList(entrySize, localVars.size()).clear();
            return visit;
        }

        @Override
        public String visitReturnStmt(GoParser.ReturnStmtContext ctx)
        {
            PDNode ret;
            if (iteration == 1) {
                ret = new PDNode();
                ret.setLineOfCode(ctx.getStart().getLine());
                ret.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(ret);
                pdNodes.put(ctx, ret);
            } else
                ret = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            if (ctx.expressionList() != null)
                analyseDefUse(ret, ctx.expressionList());
            return null;
        }

        @Override
        public String visitIdentifierList(GoParser.IdentifierListContext ctx)
        {
            StringBuilder vars = new StringBuilder();
            vars.append(visit(ctx.IDENTIFIER(0)));
            for (int i = 1; i < ctx.IDENTIFIER().size(); ++i)
            {
                vars.append(", ").append(visit(ctx.IDENTIFIER(i)));
                String init = "";
                String varID = ctx.IDENTIFIER(i).getText();
                if (ctx.IDENTIFIER(i) != null) {
                    init = visit(ctx.IDENTIFIER(i));
                    if (isUsableExpression(init))
                        useList.add(init);
                    defList.add(varID);
                    init = " $INIT " + init;
                }
            }
            return vars.toString();
        }

        @Override
        public String visitDeferStmt(GoParser.DeferStmtContext ctx)
        {
            if (analysisVisit)
                return visit(ctx.expression());
            //
            PDNode expr;
            if (iteration == 1) {
                expr = new PDNode();
                expr.setLineOfCode(ctx.getStart().getLine());
                expr.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(expr);
                pdNodes.put(ctx, expr);
            } else
                expr = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            analyseDefUse(expr, ctx.expression());
            return null;
        }

        @Override
        public String visitSendStmt(GoParser.SendStmtContext ctx)
        {
            if (analysisVisit)
                return visit(ctx.expression(0));
            //
            PDNode expr;
            if (iteration == 1) {
                expr = new PDNode();
                expr.setLineOfCode(ctx.getStart().getLine());
                expr.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(expr);
                pdNodes.put(ctx, expr);
            } else
                expr = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            analyseDefUse(expr, ctx.expression(0));
            return null;
        }

        @Override
        public String visitRecvStmt(GoParser.RecvStmtContext ctx)
        {
            if (analysisVisit)
                return visit(ctx.expression());
            //
            PDNode expr;
            if (iteration == 1) {
                expr = new PDNode();
                expr.setLineOfCode(ctx.getStart().getLine());
                expr.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(expr);
                pdNodes.put(ctx, expr);
            } else
                expr = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            analyseDefUse(expr, ctx.expression());
            return null;
        }

        @Override
        public String visitSwitchStmt(GoParser.SwitchStmtContext ctx)
        {
            //  'switch' parExpression '{' switchBlockStatementGroup* switchLabel* '}'
            //  switchBlockStatementGroup :  switchLabel+ blockStatement+
            PDNode switchNode;
            if (iteration == 1) {
                switchNode = new PDNode();
                switchNode.setLineOfCode(ctx.getStart().getLine());
                switchNode.setCode("switch " + getOriginalCodeText(ctx.exprSwitchStmt().expression()));
                ddg.addVertex(switchNode);
                pdNodes.put(ctx, switchNode);
            } else
                switchNode = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            analyseDefUse(switchNode, ctx.exprSwitchStmt().expression());
            //
            for (GoParser.ExprCaseClauseContext scx : ctx.exprSwitchStmt().exprCaseClause())
                visit(scx);
            for (GoParser.ExprSwitchCaseContext scx : ctx.exprSwitchStmt().exprSwitchCase())
                visit(scx);
            return null;
        }

        @Override
        public String visitSelectStmt(GoParser.SelectStmtContext ctx)
        {
            PDNode selectNode;
            if (iteration == 1) {
                selectNode = new PDNode();
                selectNode.setLineOfCode(ctx.getStart().getLine());
                selectNode.setCode("switch");
                ddg.addVertex(selectNode);
                pdNodes.put(ctx, selectNode);
            } else
                selectNode = (PDNode) pdNodes.get(ctx);
            //
            //
            for (GoParser.CommClauseContext scx : ctx.commClause())
                visit(scx);
            for (GoParser.CommCaseContext scx : ctx.commCase())
                visit(scx);
            return null;
        }

        @Override
        public String visitPrimaryExpr(GoParser.PrimaryExprContext ctx)
        {
            if(ctx.operand()!=null)
                return ctx.operand().getText();
            if(ctx.conversion()!=null)
                return ctx.conversion().getText();
            if(ctx.methodExpr()!=null)
                return ctx.methodExpr().getText();
            if(ctx.primaryExpr()!=null)
            {
                if(ctx.index()!=null)
                {
                    String index = visit(ctx.index().expression());
                    if (isUsableExpression(index))
                        useList.add(index);
                    return ctx.primaryExpr().getText() + '[' + index + ']';
                }
                if(ctx.slice_()!=null)
                {
                    return ctx.slice_().getText();
                }
                if(ctx.typeAssertion()!=null)
                {
                    return ctx.typeAssertion().getText();
                }
                if(ctx.arguments()!=null)
                {
                    String callee = null;
                    String callExpression = ctx.primaryExpr().getText();
                    String methodName = callExpression;
                    Logger.debug("---");
                    Logger.debug("CALL EXPR : " + methodName);
                    //
                    int start = 0, lastDot = callExpression.lastIndexOf('.');
                    if (lastDot > 0) {
                        //start = callExpression.substring(0, lastDot).lastIndexOf('.');
                        //if (start < 0)
                        //	start = 0;
                        //else
                        //	++start;
                        callee = callExpression.substring(start, lastDot);
                        Logger.debug("HAS CALLEE : " + callee);
                        if (isUsableExpression(callee)) {
                            useList.add(callee);
                            Logger.debug("CALLEE IS USABLE");
                        }
                        methodName = callExpression.substring(lastDot + 1);
                    } else {
                        Logger.debug("NO CALLEE");
                        methodName = callExpression;
                    }
                    MethodDefInfo defInfo = findDefInfo(callee, methodName, ctx.arguments().expressionList());
                    Logger.debug("FIND DEF RESULT: " + defInfo);
                    Logger.debug("---");
                    if (callee != null && defInfo != null && defInfo.doesStateDEF())
                        defList.add(callee);
                    return callExpression + '(' + visitMethodArgs(ctx.arguments().expressionList(), defInfo) + ')';
                }
            }
            return null;
        }

        @Override
        public String visitExpressionList(GoParser.ExpressionListContext ctx) {
            // expressionList : expression (',' expression)*
            StringBuilder expList = new StringBuilder(visit(ctx.expression(0)));
            for (int i = 1; i < ctx.expression().size(); ++i)
                expList.append(", ").append(visit(ctx.expression(i)));
            return expList.toString();
        }

        private String visitMethodArgs(GoParser.ExpressionListContext ctx, MethodDefInfo defInfo)
        {
            // expressionList :  expression (',' expression)*
            if (ctx != null) {
                StringBuilder args = new StringBuilder();
                List<GoParser.ExpressionContext> argsList = ctx.expression();
                String arg = visit(argsList.get(0));
                args.append(arg);
                if (isUsableExpression(arg)) {
                    useList.add(arg);
                    if (defInfo != null && defInfo.argDEFs()[0])
                        defList.add(arg);
                }
                for (int i = 1; i < argsList.size(); ++i) {
                    arg = visit(argsList.get(i));
                    args.append(", ").append(arg);
                    if (isUsableExpression(arg)) {
                        useList.add(arg);
                        if (defInfo != null && defInfo.argDEFs()[i])
                            defList.add(arg);
                    }
                }
                return args.toString();
            } else
                return "";
        }

        /*****************************************************
         *****************************************************
         ***      DETERMINANT EXPRESSIONS (NO RETURN)      ***
         *****************************************************
         *****************************************************/

        @Override
        public String visitIncDecStmt(GoParser.IncDecStmtContext ctx)
        {
            // expression ('++' | '--')
            String expr = visit(ctx.expression());
            if (isUsableExpression(expr)) {
                useList.add(expr);
                defList.add(expr);
            }
            if (ctx.PLUS_PLUS()!=null)
                return expr + "++";
            else
                return expr + "--";
        }

        @Override
        public String visitExpression(GoParser.ExpressionContext ctx)
        {
            if(ctx.unary_op!=null)
            {
                String expr = visit(ctx.expression(0));
                if (isUsableExpression(expr))
                    useList.add(expr);
                if (ctx.EXCLAMATION() != null)
                    return '!' + expr;
                else if (ctx.PLUS() != null)
                    return '+' + expr;
                else if (ctx.MINUS() != null)
                    return '-' + expr;
                else if (ctx.CARET() != null)
                    return '^' + expr;
                else if (ctx.STAR() != null)
                    return '*' + expr;
                else if (ctx.AMPERSAND() != null)
                    return '&' + expr;
                else
                    return "<-" + expr;
            }
            if(ctx.mul_op!=null)
            {
                String expr1 = visit(ctx.expression(0));
                String expr2 = visit(ctx.expression(1));
                if (isUsableExpression(expr1))
                    useList.add(expr1);
                if (isUsableExpression(expr2))
                    useList.add(expr2);
                String op=" ";
                if(ctx.STAR()!=null)
                    op = " $STAR ";
                else if(ctx.DIV()!=null)
                    op = " $DIV ";
                else if(ctx.MOD()!=null)
                    op = " $MOD ";
                else if(ctx.LSHIFT()!=null)
                    op = " $LSHIFT ";
                else if(ctx.RSHIFT()!=null)
                    op = " $RSHIFT ";
                else if(ctx.AMPERSAND()!=null)
                    op = " $AMPERSAND ";
                else if(ctx.BIT_CLEAR()!=null)
                    op = " $BIT_CLEAR";
                // the parethesis are added to mark this expression as used
                return '(' + expr1 + op + expr2 + ')';
            }
            if(ctx.add_op!=null)
            {
                String expr1 = visit(ctx.expression(0));
                String expr2 = visit(ctx.expression(1));
                if (isUsableExpression(expr1))
                    useList.add(expr1);
                if (isUsableExpression(expr2))
                    useList.add(expr2);
                String op=" ";
                if(ctx.PLUS()!=null)
                    op = " $PLUS ";
                else if(ctx.MINUS()!=null)
                    op = " $MINUS ";
                else if(ctx.OR()!=null)
                    op = " $OR ";
                else if(ctx.CARET()!=null)
                    op = " $CARET ";
                return '(' + expr1 + op + expr2 + ')';
            }
            if(ctx.rel_op!=null)
            {
                String expr1 = visit(ctx.expression(0));
                String expr2 = visit(ctx.expression(1));
                if (isUsableExpression(expr1))
                    useList.add(expr1);
                if (isUsableExpression(expr2))
                    useList.add(expr2);
                String op=" ";
                if(ctx.EQUALS()!=null)
                    op = " $EQUALS ";
                else if(ctx.NOT_EQUALS()!=null)
                    op = " $NOT_EQUALS ";
                else if(ctx.LESS()!=null)
                    op = " $LESS ";
                else if(ctx.LESS_OR_EQUALS()!=null)
                    op = " $LESS_OR_EQUALS ";
                else if(ctx.GREATER()!=null)
                    op = " $GREATER ";
                else if(ctx.GREATER_OR_EQUALS()!=null)
                    op = " $GREATER_OR_EQUALS ";
                return '(' + expr1 + op + expr2 + ')';
            }
            if(ctx.LOGICAL_AND()!=null)
            {
                String expr1 = visit(ctx.expression(0));
                String expr2 = visit(ctx.expression(1));
                if (isUsableExpression(expr1))
                    useList.add(expr1);
                if (isUsableExpression(expr2))
                    useList.add(expr2);
                String op=" $LOGICAL_AND ";
                return '(' + expr1 + op + expr2 + ')';
            }
            if(ctx.LOGICAL_OR()!=null)
            {
                String expr1 = visit(ctx.expression(0));
                String expr2 = visit(ctx.expression(1));
                if (isUsableExpression(expr1))
                    useList.add(expr1);
                if (isUsableExpression(expr2))
                    useList.add(expr2);
                String op=" $LOGICAL_OR ";
                return '(' + expr1 + op + expr2 + ')';
            }
            else
            {
                return visit(ctx.primaryExpr());
            }

        }

        @Override
        public String visitAssignment(GoParser.AssignmentContext ctx)
        {
            String expr ="";
            visit(ctx.expressionList(1));
            String expr3 = "";
            int size=0;
            while (size<ctx.expressionList(0).expression().size())
            {

                String expr1 = ctx.expressionList(0).expression(size).getText();
                if(isUsableExpression(expr1))
                {
                    useList.add(expr1);
                    expr=expr + " "+expr1;
                }
                size++;
            }
            int size1=size;
            size =0;
            while(size<ctx.expressionList(1).expression().size())
            {
                String expr2 = ctx.expressionList(1).expression(size).primaryExpr().getText();
                if (isUsableExpression(expr2))
                {
                    useList.add(expr2);
                    expr3=expr3 + " "+expr2;
                    int size2=0;
                    while(size2<size1)
                    {
                        if(Objects.equals(ctx.expressionList(0).expression(size2).getText(), expr2))
                    {
                        if(ctx.assign_op().ASSIGN()!=null)
                        defList.add(expr2);
                    }
                        size2++;
                    }
                }
                size++;
            }
            // the parethesis are added to mark this expression as used
            return '(' + expr + " $ASSIGN " + expr3 + ')';
        }


    }
}








class MethodDefInfo {

    // Method ID
    public final String NAME;
    public final String PACKAGE;
    public final String RET_TYPE;
    public final String CLASS_NAME;
    public final String[] PARAM_TYPES;

    // DEF Info
    private boolean stateDEF;
    private boolean[] argDEFs;
    private List<String> fieldDEFs;

    public MethodDefInfo(String ret, String name, String pkg, String cls, String[] args) {
        NAME = name;
        RET_TYPE = ret;
        CLASS_NAME = cls;
        PACKAGE = pkg == null ? "" : pkg;
        PARAM_TYPES = args == null ? new String[0] : args;
        //
        fieldDEFs = new ArrayList<>();
        stateDEF = guessByTypeOrName();
        argDEFs = new boolean[PARAM_TYPES.length];  // all initialized to 'false'
    }

    private boolean guessByTypeOrName() {
        // First check if this method is a constructor ...
        if (RET_TYPE == null)
            return true;
        // If not, then try to guess by method-name ...
        String[] prefixes = { "set", "put", "add", "insert", "push", "append" };
        for (String pre: prefixes)
            if (NAME.toLowerCase().startsWith(pre))
                return true;
        return false;
    }

    public boolean doesStateDEF() {
        return stateDEF;
    }

    public void setStateDEF(boolean stateDef) {
        stateDEF = stateDef;
    }

    public boolean[] argDEFs() {
        return argDEFs;
    }

    public void setArgDEF(int argIndex, boolean def) {
        argDEFs[argIndex] = def;
    }

    public void setAllArgDEFs(boolean[] argDefs) {
        argDEFs = argDefs;
    }

    public String[] fieldDEFs() {
        return fieldDEFs.toArray(new String[fieldDEFs.size()]);
    }

    public void addFieldDEF(String fieldName) {
        if (!fieldDEFs.contains(fieldName)) {
            fieldDEFs.add(fieldName);
            stateDEF = true;
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof MethodDefInfo))
            return false;
        MethodDefInfo info = (MethodDefInfo) obj;
        return this.NAME.equals(info.NAME)
                && this.PACKAGE.equals(info.PACKAGE) && this.RET_TYPE.equals(info.RET_TYPE)
                && Arrays.equals(this.PARAM_TYPES, info.PARAM_TYPES);
    }

    @Override
    public String toString() {
        String retType = RET_TYPE == null ? "null" : RET_TYPE;
        String args = PARAM_TYPES == null ? "null" : Arrays.toString(PARAM_TYPES);
        StringBuilder str = new StringBuilder();
        str.append("{ TYPE : \"").append(retType).append("\", ");
        str.append("NAME : \"").append(NAME).append("\", ");
        str.append("ARGS : ").append(args).append(", ");
        str.append("PACKAGE : \"").append(PACKAGE).append("\" }");
        return str.toString();
    }
}